// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package aliyun

import (
	"fmt"
	"strings"
	"time"

	"yunion.io/x/jsonutils"
	"yunion.io/x/log"
	"yunion.io/x/pkg/util/netutils"
	"yunion.io/x/pkg/util/rbacscope"
	"yunion.io/x/pkg/utils"

	api "yunion.io/x/cloudmux/pkg/apis/compute"
	"yunion.io/x/cloudmux/pkg/cloudprovider"
	"yunion.io/x/cloudmux/pkg/multicloud"
)

// {"AvailableIpAddressCount":4091,"CidrBlock":"172.31.32.0/20","CreationTime":"2017-03-19T13:37:44Z","Description":"System created default virtual switch.","IsDefault":true,"Status":"Available","VSwitchId":"vsw-j6c3gig5ub4fmi2veyrus","VSwitchName":"","VpcId":"vpc-j6c86z3sh8ufhgsxwme0q","ZoneId":"cn-hongkong-b"}

const (
	VSwitchPending   = "Pending"
	VSwitchAvailable = "Available"
)

type SCloudResources struct {
	CloudResourceSetType []string
}

type SVSwitch struct {
	multicloud.SNetworkBase
	AliyunTags
	wire *SWire

	AvailableIpAddressCount int

	CidrBlock     string
	Ipv6CidrBlock string
	EnabledIpv6   bool
	CreationTime  time.Time
	Description   string
	IsDefault     bool
	Status        string
	VSwitchId     string
	VSwitchName   string
	VpcId         string
	ZoneId        string

	CloudResources  SCloudResources
	ResourceGroupId string
	RouteTable      SRouteTable
}

func (self *SVSwitch) GetId() string {
	return self.VSwitchId
}

func (self *SVSwitch) GetName() string {
	if len(self.VSwitchName) > 0 {
		return self.VSwitchName
	}
	return self.VSwitchId
}

func (self *SVSwitch) GetGlobalId() string {
	return self.VSwitchId
}

func (self *SVSwitch) GetStatus() string {
	return strings.ToLower(self.Status)
}

func (self *SVSwitch) Refresh() error {
	net, err := self.wire.zone.region.GetVSwitchAttributes(self.VSwitchId)
	if err != nil {
		return err
	}
	return jsonutils.Update(self, net)
}

func (self *SVSwitch) GetIWire() cloudprovider.ICloudWire {
	return self.wire
}

func (self *SVSwitch) SetTags(tags map[string]string, replace bool) error {
	return self.wire.zone.region.SetResourceTags(ALIYUN_SERVICE_VPC, "VSWITCH", self.VSwitchId, tags, replace)
}

func (net *SVSwitch) GetIp6Start() string {
	if len(net.Ipv6CidrBlock) > 0 {
		prefix, err := netutils.NewIPV6Prefix(net.Ipv6CidrBlock)
		if err != nil {
			return ""
		}
		return prefix.Address.NetAddr(prefix.MaskLen).StepUp().String()
	}
	return ""
}

func (net *SVSwitch) GetIp6End() string {
	if len(net.Ipv6CidrBlock) > 0 {
		prefix, err := netutils.NewIPV6Prefix(net.Ipv6CidrBlock)
		if err != nil {
			return ""
		}
		end := prefix.Address.NetAddr(prefix.MaskLen).BroadcastAddr(prefix.MaskLen)
		for i := 0; i < 15; i++ {
			end = end.StepDown()
		}
		return end.String()
	}
	return ""
}

func (net *SVSwitch) GetIp6Mask() uint8 {
	if len(net.Ipv6CidrBlock) > 0 {
		prefix, err := netutils.NewIPV6Prefix(net.Ipv6CidrBlock)
		if err != nil {
			return 0
		}
		return prefix.MaskLen
	}
	return 0
}

func (net *SVSwitch) GetGateway6() string {
	if len(net.Ipv6CidrBlock) > 0 {
		prefix, err := netutils.NewIPV6Prefix(net.Ipv6CidrBlock)
		if err != nil {
			return ""
		}
		return prefix.Address.NetAddr(prefix.MaskLen).StepUp().String()
	}
	return ""
}

func (self *SVSwitch) GetIpStart() string {
	pref, _ := netutils.NewIPV4Prefix(self.CidrBlock)
	startIp := pref.Address.NetAddr(pref.MaskLen) // 0
	startIp = startIp.StepUp()                    // 1
	return startIp.String()
}

func (self *SVSwitch) GetIpEnd() string {
	pref, _ := netutils.NewIPV4Prefix(self.CidrBlock)
	endIp := pref.Address.BroadcastAddr(pref.MaskLen) // 255
	endIp = endIp.StepDown()                          // 254
	endIp = endIp.StepDown()                          // 253
	endIp = endIp.StepDown()                          // 252
	return endIp.String()
}

func (self *SVSwitch) GetIpMask() int8 {
	pref, _ := netutils.NewIPV4Prefix(self.CidrBlock)
	return pref.MaskLen
}

func (self *SVSwitch) GetGateway() string {
	pref, _ := netutils.NewIPV4Prefix(self.CidrBlock)
	endIp := pref.Address.BroadcastAddr(pref.MaskLen) // 255
	endIp = endIp.StepDown()                          // 254
	return endIp.String()
}

func (self *SVSwitch) GetServerType() string {
	return api.NETWORK_TYPE_GUEST
}

func (self *SVSwitch) GetIsPublic() bool {
	// return self.IsDefault
	return true
}

func (self *SVSwitch) GetPublicScope() rbacscope.TRbacScope {
	return rbacscope.ScopeDomain
}

func (self *SRegion) createVSwitch(zoneId string, vpcId string, name string, cidr string, desc string) (string, error) {
	params := make(map[string]string)
	params["ZoneId"] = zoneId
	params["VpcId"] = vpcId
	params["CidrBlock"] = cidr
	params["VSwitchName"] = name
	if len(desc) > 0 {
		params["Description"] = desc
	}
	params["ClientToken"] = utils.GenRequestId(20)

	body, err := self.vpcRequest("CreateVSwitch", params)
	if err != nil {
		return "", err
	}
	return body.GetString("VSwitchId")
}

func (self *SRegion) DeleteVSwitch(vswitchId string) error {
	params := make(map[string]string)
	params["VSwitchId"] = vswitchId

	_, err := self.vpcRequest("DeleteVSwitch", params)
	return err
}

func (self *SVSwitch) Delete() error {
	err := self.Refresh()
	if err != nil {
		log.Errorf("refresh vswitch fail %s", err)
		return err
	}
	if len(self.RouteTable.RouteTableId) > 0 && !self.RouteTable.IsSystem() {
		err = self.wire.zone.region.UnassociateRouteTable(self.RouteTable.RouteTableId, self.VSwitchId)
		if err != nil {
			log.Errorf("unassociate routetable fail %s", err)
			return err
		}
	}
	err = self.dissociateWithSNAT()
	if err != nil {
		log.Errorf("fail to dissociateWithSNAT")
		return err
	}
	err = cloudprovider.Wait(10*time.Second, 60*time.Second, func() (bool, error) {
		err := self.wire.zone.region.DeleteVSwitch(self.VSwitchId)
		if err != nil {
			// delete network immediately after deleting vm on it
			// \"Code\":\"DependencyViolation\",\"Message\":\"Specified object has dependent resources.\"}
			if isError(err, "DependencyViolation") {
				return false, nil
			}
			return false, err
		} else {
			return true, nil
		}
	})
	return err
}

func (self *SVSwitch) GetAllocTimeoutSeconds() int {
	return 120 // 2 minutes
}

func (self *SRegion) GetVSwitches(ids []string, vpcId string, offset int, limit int) ([]SVSwitch, int, error) {
	if limit > 50 || limit <= 0 {
		limit = 50
	}
	params := make(map[string]string)
	params["RegionId"] = self.RegionId
	params["PageSize"] = fmt.Sprintf("%d", limit)
	params["PageNumber"] = fmt.Sprintf("%d", (offset/limit)+1)
	if ids != nil && len(ids) > 0 {
		params["VSwitchId"] = strings.Join(ids, ",")
	}
	if len(vpcId) > 0 {
		params["VpcId"] = vpcId
	}

	body, err := self.vpcRequest("DescribeVSwitches", params)
	if err != nil {
		log.Errorf("GetVSwitches fail %s", err)
		return nil, 0, err
	}

	switches := make([]SVSwitch, 0)
	err = body.Unmarshal(&switches, "VSwitches", "VSwitch")
	if err != nil {
		log.Errorf("Unmarshal vswitches fail %s", err)
		return nil, 0, err
	}
	total, _ := body.Int("TotalCount")
	return switches, int(total), nil
}

func (self *SRegion) GetVSwitchAttributes(idstr string) (*SVSwitch, error) {
	params := make(map[string]string)
	params["VSwitchId"] = idstr

	body, err := self.vpcRequest("DescribeVSwitchAttributes", params)
	if err != nil {
		log.Errorf("DescribeVSwitchAttributes fail %s", err)
		return nil, err
	}
	if self.client.debug {
		log.Debugf("%s", body.PrettyString())
	}
	switches := SVSwitch{}
	err = body.Unmarshal(&switches)
	if err != nil {
		log.Errorf("Unmarshal vswitches fail %s", err)
		return nil, err
	}
	return &switches, nil
}

func (vsw *SVSwitch) dissociateWithSNAT() error {
	natgatways, err := vsw.wire.vpc.getNatGateways()
	if err != nil {
		return err
	}
	for i := range natgatways {
		err = natgatways[i].dissociateWithVswitch(vsw.VSwitchId)
		if err != nil {
			return err
		}
	}
	return nil
}

func (self *SVSwitch) GetProjectId() string {
	return self.ResourceGroupId
}
